﻿#region License

// Another free, open solution by someone who gets it.
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

using Patterns.Text;
using Patterns.Validation.Advanced;

namespace Patterns.Reflection
{
	/// <summary>
	/// 	Provides a simple bridge between the MemberInfo and String classes (useful for reflection scenarios)
	/// </summary>
	public class MemberName
	{
		private const string _typeNameKey = "typeName";
		private const string _memberNameKey = "memberName";
		private const string _memberArgsKey = "memberArgs";
		private const string _returnTypeKey = "returnType";
		private const string _patternFormat = @"^((?<{0}>[^:]+)::)?(?<{1}>[^\(:]+)(\((?<{2}>[^\)]+)\))?(:(?<{3}>.+))?$";

		private static readonly CompiledRegex _argSplitter = @"\s*,\s*";

		private static readonly CompiledRegex _pattern = string.Format(_patternFormat, _typeNameKey, _memberNameKey,
		                                                               _memberArgsKey,
		                                                               _returnTypeKey);

		private readonly IDictionary<string, string> _nameAnalysisResults;
		private MemberInfo _candidateMember;

		private MemberName(IDictionary<string, string> nameAnalysisResults)
		{
			_nameAnalysisResults = nameAnalysisResults;
			Name = _nameAnalysisResults[_memberNameKey];

			string typeName = _nameAnalysisResults[_typeNameKey];
			if (!string.IsNullOrEmpty(typeName)) OwnerType = typeName;
		}

		///<summary>
		///	Gets or sets the name of the member.
		///</summary>
		public string Name{ get; set; }

		///<summary>
		///	Gets or sets the type of the member's owner.
		///</summary>
		public TypeName OwnerType{ get; set; }

		///<summary>
		///	Returns a <see cref = "MemberInfo" /> instance that represents the member given a target type.
		///</summary>
		///<param name = "instanceType">The target type.</param>
		///<returns>A <see cref = "MemberInfo" /> instance representing the member if the member can be found; otherwise, null.</returns>
		public MemberInfo ResolveMember(Type instanceType)
		{
			if (OwnerType != null && OwnerType.Type != null) return !OwnerType.Type.IsAssignableFrom(instanceType) ? null : _candidateMember;

			ResolveMemberInfo(instanceType);

			return _candidateMember;
		}

		///<summary>
		///	Provides an implicit conversion from the <see cref = "string" /> type to the <see cref = "MemberName" /> type.
		///</summary>
		///<param name = "memberName">The string member name.</param>
		///<returns>The converted <see cref = "MemberName" /> instance.</returns>
		public static implicit operator MemberName(string memberName)
		{
			memberName.Should().NotBeNullOrEmpty();

			IDictionary<string, string> match = _pattern.DictionaryMatch(memberName);

			match.Should().NotBeEmpty("the name must match the pattern defined for member names");

			var result = new MemberName(match);

			result.Name.Should().NotBeNullOrEmpty();

			if (result.OwnerType != null && result.OwnerType.Type != null) result.ResolveMemberInfo(result.OwnerType.Type);

			return result;
		}

		private void ResolveMemberInfo(IReflect ownerType)
		{
			ICollection<Predicate<MemberInfo>> predicates = new List<Predicate<MemberInfo>>
			{
				m => StringComparer.OrdinalIgnoreCase.Equals(m.Name, Name)
			};

			string returnType = _nameAnalysisResults[_returnTypeKey];

			if (!string.IsNullOrEmpty(returnType))
			{
				TypeName returnTypeName = returnType;
				if (returnTypeName.Type != null) predicates.Add(m => m.GetReturnType() == returnTypeName.Type);
			}

			string memberArgs = _nameAnalysisResults[_memberArgsKey];

			if (!string.IsNullOrEmpty(memberArgs))
			{
				Type[] argTypes = _argSplitter.Split(memberArgs)
					.Select(arg => ((TypeName) arg).Type)
					.ToArray();

				predicates.Add(m => argTypes.SequenceEqual(m.GetArgumentTypes()));
			}

			_candidateMember = ownerType.GetMembers(MemberVisibility.All)
				.Where(m => predicates.All(isValid => isValid(m)))
				.FirstOrDefault();
		}
	}
}